package simpleftp.client.control;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import simpleftp.client.gui.FTPClientWindow;

public class CommandDispatcher implements Runnable {

    public static final String NL = "\n";
    private static final String USER = "USER";
    private static final String PASS = "PASS";
    private static final String SERVER = "SERVER";
    private static final String PORT = "PORT";
    private static final String SETTINGS_ROOT = "simpleftpsettings";
    private static final String DEFAULT_SETTINGS = SETTINGS_ROOT + "/default";
    private static final String LAST_SETTINGS = SETTINGS_ROOT + "/last";
    private static final String DEF_USER = "paris";
    private static final String DEF_PASS = "paris";
    private static final String DEF_SERVER = "127.0.0.1";
    private static final String DEF_PORT = "7802";
    private final ExecutorService executorPool;
    private final CompletionService<Boolean> completionPool;
    private final FTPClientWindow window;
    private final int numberOfCores;
    private final double blockingCoefficient = 0.9;
    private final int poolSize;
    private final BlockingQueue<Command> commands;
    private final SimpleFTP client;
    private Preferences userPrefs;
    private String server;
    private String port;
    private String user;
    private String pass;
    private boolean alreadyConnected;

    public CommandDispatcher(BlockingQueue<Command> cmd, FTPClientWindow wind) {
        this.commands = cmd;
        this.window = wind;
        alreadyConnected = false;
        numberOfCores = Runtime.getRuntime().availableProcessors();
        poolSize = (int) (numberOfCores / (1 - blockingCoefficient));
        executorPool = Executors.newFixedThreadPool(poolSize);
        completionPool = new ExecutorCompletionService<>(executorPool);
        client = new SimpleFTP();
    }

    @Override
    public void run() {
        try {
            loadStartupPreferences();
            Command cmd = this.commands.take();
            while (!cmd.equals(Command.QUIT)) {
                switch (cmd) {
                    case CONNECT:
                        connect();
                        break;
                    case DISCONNECT:
                        disconnect();
                        break;
                    case STOR:
                        upload();
                        break;
                    case LIST:
                        listDir();
                        break;
                    case RETR:
                        download();
                        break;
                    case PWD:
                        pwd();
                        break;
                    case CWD:
                        cwd();
                        break;
                    case CLEAR:
                        clearConsole();
                        break;
                    case LOADPREFS:
                        loadPreferences();
                        break;
                    case SAVEPREFS:
                        savePreferences();
                        break;
                    case DELPREF:
                        deletePreference();
                        break;
                    default:
                        break;
                }
                cmd = this.commands.take();
            }
            saveLastPreferences();
            disconnect();
            executorPool.shutdown();
        } catch (InterruptedException e) {
        }
    }

    private void clearConsole() {
        window.console.setText("");
    }

    private void saveLastPreferences() {
        userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
        userPrefs.put(USER, window.tfusername.getText());
        userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
        userPrefs.put(SERVER, window.tfservername.getText());
        userPrefs.put(PORT, window.tfport.getText());
        try {
            userPrefs.flush();
        } catch (BackingStoreException e) {
        }
    }

    private void loadStartupPreferences() {
        try {
            userPrefs = Preferences.userRoot();
            if (!userPrefs.nodeExists(DEFAULT_SETTINGS)) {
                userPrefs = Preferences.userRoot().node(DEFAULT_SETTINGS);
                userPrefs.put(USER, DEF_USER);
                userPrefs.put(PASS, DEF_PASS);
                userPrefs.put(SERVER, DEF_SERVER);
                userPrefs.put(PORT, DEF_PORT);
                userPrefs.flush();
            }
            if (userPrefs.nodeExists(LAST_SETTINGS)) {
                userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
            }
            window.tfusername.setText(userPrefs.get(USER, DEF_USER));
            window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
            window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
            window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
        } catch (BackingStoreException e) {
        }
    }

    private void loadPreferences() {
        try {
            userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
            String[] userSavedPrefs = userPrefs.childrenNames();
            String s = (String) JOptionPane.showInputDialog(window,
                    "Choose preference to load:", "Load Preferences",
                    JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
            if ((s != null) && (s.length() > 0)) {
                userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
                window.tfusername.setText(userPrefs.get(USER, DEF_USER));
                window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
                window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
                window.tfport.setText(userPrefs.get(PORT, DEF_PORT));

                window.console.append(NL + "User preferences " + s + " loaded.");

            } else {
                window.console.append(NL + "Preferences loading operation cancelled.");

            }
        } catch (BackingStoreException e) {
        }
    }

    private void savePreferences() {
        String s = (String) JOptionPane.showInputDialog(window,
                "Save the current connection info as:", "Save Preferences",
                JOptionPane.QUESTION_MESSAGE, null, null, null);
        if ((s != null) && (s.length() > 0)) {
            userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
            userPrefs.put(USER, window.tfusername.getText());
            userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
            userPrefs.put(SERVER, window.tfservername.getText());
            userPrefs.put(PORT, window.tfport.getText());
            try {
                userPrefs.flush();
            } catch (BackingStoreException e) {
                window.console.append(NL + "Could not save preferences: "
                        + e.getMessage());
            }
            window.console.append(NL + "Preferences saved as " + s);

        } else {
            window.console.append(NL + "Preferences saving operation cancelled.");

        }
    }

    private void deletePreference() {
        try {
            userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
            String[] userSavedPrefs = userPrefs.childrenNames();
            String s = (String) JOptionPane.showInputDialog(window,
                    "Choose preference to delete:", "Delete Preference",
                    JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
            if ((s != null) && (s.length() > 0)) {
                userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
                userPrefs.removeNode();
                userPrefs.flush();
                window.console.append(NL + "User preferences " + s + " deleted.");

            } else {
                window.console.append(NL + "Preferences deletion operation cancelled.");

            }
        } catch (BackingStoreException e) {
        }
    }

    private void download() {

        if (!alreadyConnected) {
            window.console.append(NL + "You are not connected to any server.");
            return;
        }

        DefaultMutableTreeNode newNode = (DefaultMutableTreeNode) window.tree
                .getLastSelectedPathComponent();

        if (newNode == null) {
            return;
        }

		//Object nodeInfo = newNode.getUserObject();
        if (newNode.isLeaf()) {

            final JFileChooser fc = new JFileChooser();
            fc.setFileSelectionMode(JFileChooser.SAVE_DIALOG);
            fc.setDragEnabled(true);
            int returnVal = fc.showDialog(window, "Download");
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                //String path = window.tree.getSelectionPath().toString();

                TreePath TreePathList[] = window.tree.getSelectionPaths();
                for (TreePath TreePathList1 : TreePathList) {
                    System.out.println(TreePathList1.toString());
                }

                String SelectedFiles[] = new String[TreePathList.length];

                for (int i = 0; i < SelectedFiles.length; i++) {
                    SelectedFiles[i] = standPath(TreePathList[i].toString());
                    System.out.println(SelectedFiles[i]);
                }

                File[] files = new File[TreePathList.length];

                for (int i = 0; i < TreePathList.length; i++) {
                    files[i] = new File(SelectedFiles[i]);
                }

                for (File f : files) {

                    if (f.isFile()) {
                        System.out.println("It's a file");
                        List<Future<Boolean>> downloaders = new ArrayList<>(files.length);

                        {
                            Downloader downloader = new Downloader(f, window, server, port, user, pass, fc.getSelectedFile().getAbsolutePath());
                            downloaders.add(completionPool.submit(downloader));
                        }
                    } else {
                        System.out.println("It's a folder");
                        window.console.append(NL + "Please choose a file.");
                    }
                }
            }
        } else {
            window.console.append(NL + "Download action cancelled.");
        }

    }

    private String standPath(String st) {
        st = st.replace("]", "");
        String path[] = st.split(", ");
        String pathFinal = "";

        if (path.length > 2) {
            for (int i = 2; i < path.length; i++) {
                pathFinal += path[i];
            }
        }

        if (path.length == 2) {
            for (int i = 1; i < path.length; i++) {
                pathFinal += path[i];
            }
            pathFinal += System.getProperty("file.separator") + ".." + System.getProperty("file.separator");
        }

        return pathFinal;
    }

    private void upload() {
        if (!alreadyConnected) {
            window.console.append(NL + "You are not connected to any server.");

            return;
        }
        final JFileChooser fc = new JFileChooser();
        fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
        fc.setMultiSelectionEnabled(true);
        fc.setDragEnabled(true);
        int returnVal = fc.showDialog(window, "Upload");
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File[] files = fc.getSelectedFiles();
            List<Future<Boolean>> uploaders = new ArrayList<>(
                    files.length);
            for (File f : files) {
                Uploader uploader = new Uploader(f, window, server, port, user,
                        pass);
                uploaders.add(completionPool.submit(uploader));
            }
        } else {
            window.console.append(NL + "Upload action cancelled.");

        }
    }

//File system, uncomment & comment JSON system to have the file system in a filetree
	/*private void listDir() {
     if (!alreadyConnected) {
     window.console.append(NL + "You are not connected to any server.");
     consoleToEnd();
     return;
     }
     try {
     String dirContent = client.ls();
     window.console.append(NL + dirContent);
     consoleToEnd();
     File f = client.getFileTree();
     window.root.removeAllChildren();
     DefaultMutableTreeNode userRoot = new DefaultMutableTreeNode(f.getName());
     populateTree(userRoot, f);
     window.root.add(userRoot);
     //window.modelTree.reload();
     window.tree.expandRow(0);
     } catch (IOException e) {
     window.console.append(NL + e.getMessage());
     consoleToEnd();
     e.printStackTrace();
     }

     }
	
     private void populateTree(DefaultMutableTreeNode userRoot, File root) throws IOException {
     // TO BE COMPLETED - WRITE THIS RECURSIVE METHOD - 7 LINES    
		
     File[] list = root.listFiles();
     if(list == null)
     System.out.println("NULL");
     else {
     DefaultMutableTreeNode subNode;
     for(File nom : list)
     {
     if(nom.isDirectory())
     {
     subNode = new DefaultMutableTreeNode(nom.getName()+System.getProperty("file.separator"));
     this.populateTree(subNode, nom);
     }
     else
     {
     subNode = new DefaultMutableTreeNode(nom.getName());
     }
     userRoot.add(subNode);
     }
     }
     }*/
	//END FILE
//JSON system, it's applied by default, if you want to use filetree comment this & uncomment file system above
    private void listDir() {
        if (!alreadyConnected) {
            window.console.append(NL + "You are not connected to any server.");

            return;
        }
        try {
            String dirContent = client.ls();
            window.console.append(NL + dirContent);

            JSONObject jsonob = client.getJSONObject();
            window.root.removeAllChildren();
            DefaultMutableTreeNode userRoot = new DefaultMutableTreeNode(jsonob.get("name"));
            populateTreeJSON(userRoot, jsonob);
            window.root.add(userRoot);
            window.tree.expandRow(0);
        } catch (IOException e) {
            window.console.append(NL + e.getMessage());
        }

    }

    private void populateTreeJSON(DefaultMutableTreeNode userRoot, JSONObject json) {
        JSONArray files = (JSONArray) json.get(json.get("name"));
        JSONObject object = null;
        DefaultMutableTreeNode newNode = null;

        if (files == null) {
            System.out.println("NULL JSON");
        }
        for (int i = 0; i < files.size(); i++) {
            object = (JSONObject) files.get(i);
            if ("false".equals(object.get("folder").toString())) {
                newNode = new DefaultMutableTreeNode(object.get("name").toString());
            }
            if ("true".equals(object.get("folder").toString())) {
                newNode = new DefaultMutableTreeNode(object.get("name").toString() + System.getProperty("file.separator"));
                this.populateTreeJSON(newNode, object);
            }
            userRoot.add(newNode);
        }
    }
// END JSON SYSTEM

    private void pwd() {
        try {
            if (!alreadyConnected) {
                window.console.append(NL + "You are not connected to any server.");

                return;
            }
            String curDir = client.pwd();
            window.console.append(NL + "Current directory on FTP server: " + curDir);

        } catch (IOException e) {
            window.console.append(NL + e.getMessage());
        }
    }

    private void cwd() {
        try {
            if (!alreadyConnected) {
                window.console.append(NL + "You are not connected to any server.");

                return;
            }

            String st = window.tree.getSelectionPath().toString();

            st = st.replace("]", "");
            String path1[] = st.split(", ");
            String pathFinal = "";

            if (path1.length > 2) {
                for (int i = 2; i < path1.length; i++) {
                    pathFinal += path1[i];
                }
            }
            if (path1.length == 2) {
                for (int i = 1; i < path1.length; i++) {
                    pathFinal += path1[i];
                }
                pathFinal += System.getProperty("file.separator") + ".." + System.getProperty("file.separator");
            }

            File fnewRoot = new File(pathFinal);

            if (fnewRoot.isDirectory()) {
                client.cwd(pathFinal);
                window.console.append(NL + "New Current directory: " + pathFinal);

            }

        } catch (IOException e) {
            window.console.append(NL + e.getMessage());
        }
    }

    private void disconnect() {
        try {
            if (alreadyConnected) {
                client.disconnect();
                alreadyConnected = false;
                window.console.append(NL + "Now disconnected");

            } else {
                window.console.append(NL + "Already disconnected");

            }
        } catch (IOException e) {
            window.console.append(NL + e.getMessage());
        }

    }

    private void connect() {
        if (alreadyConnected) {
            window.console.append(NL
                    + "You are already connected to this server.");

            return;
        }
        server = window.tfservername.getText();
        if (server == null || "".equals(server)) {
            window.console.append(NL
                    + "You must specify the server IP address.");

            return;
        }
        port = window.tfport.getText();
        if (port == null || "".equals(port)) {
            window.console.append(NL
                    + "You must specify the server port I must connect to.");

            return;
        }
        user = window.tfusername.getText();
        if (user == null || "".equals(user)) {
            user = "anonymous";
        }
        pass = new String(window.pfuserpass.getPassword());
        if (pass == null || "".equals(pass)) {
            pass = "anonymous";
        }
        try {
            client.connect(server, Integer.parseInt(port), user, pass);
            alreadyConnected = true;
            window.console.append(NL + "Now connected");

            pwd();
            listDir();
        } catch (IOException | NumberFormatException e) {
            window.console.append(NL + e.getMessage());
        }
    }
}
